#include "disk.h"
#include <fstream>
#include <iostream>
#include <string>
#include <iomanip>
#include <cmath>
#include "config.h"
#include "util.h"

using std::ios;

Disk::Disk()
{

}

Disk::Disk(string file_name, int capacity)
{
    file_name_ = file_name;
    file_stream_.open(file_name_, ios::in | ios::out);
    capacity_ = capacity;
    fat_size_ = capacity_ / 64;
    available_block_start_ = ceil(fat_size_/64);
    InitFatCache();
}

void Disk::InitFatCache() {
    file_stream_.seekg(0, ios::beg);
    int value;
    for (int byte_id = 0; byte_id < fat_size_; byte_id ++) {
        file_stream_ >> std::setbase(16) >> value;
        fat_cache_[byte_id] = value;
    }
}

int Disk::Write(string content) {
    // TODO 查看有没有足够的空盘块

    if (content == "") {
        for(unsigned int itr = 0; itr < fat_cache_.size(); itr ++) {
            if (GetFatValue(itr) == 0) {
                UpdateFat(itr, GetFileEndFlag());;
                return itr;
            }
        }
        return 0;
    }

    int data_length = content.length();
    int content_ptr = 0;
    int front_block = -1;
    int subcontent_length = 0;
    int start_block = 0;

    string subcontent;

    for(unsigned int itr = 0; itr < fat_cache_.size() && data_length > 0; itr ++) {
        if (GetFatValue(itr) == 0) {
            subcontent_length = data_length>BLOCK_SIZE?BLOCK_SIZE:data_length;
            subcontent = content.substr(content_ptr, subcontent_length);
            content_ptr = content_ptr + subcontent_length;
            data_length -= subcontent_length;

            WriteToBlock(itr, subcontent);
            UpdateFat(front_block, itr);
            if (!start_block) start_block = itr;
            front_block = itr;
        }
    }
    UpdateFat(front_block, GetFileEndFlag());
    return start_block;
}

void Disk::WriteToBlock(int block_id, string content) {
    //定位到块开始
    int offset = GetBlockOffset(block_id);
    int cur_byte = 0;
    file_stream_.seekp(offset, ios::beg);
    for (unsigned int i = 0; i < content.length(); i ++) {
        cur_byte = ((int)content[i]) & 255;
        file_stream_ << std::setbase(16) << cur_byte;
        file_stream_ << " ";
    }
    for (unsigned int i = content.length(); i < BLOCK_SIZE; i ++) {
        file_stream_ << "00 ";
    }
    file_stream_.flush();
    //file_stream_.close();
}

void Disk::ReadFromBlock(int block_id, string &buffer) {
    int offset = GetBlockOffset(block_id);
    file_stream_.seekp(offset, ios::beg);
    int hex;
    int first_zero = -1;
    int last_zero = BLOCK_SIZE - 1;
    bool first_last_still_zero = false;
    for (int i = 0; i < BLOCK_SIZE; i ++) {
        file_stream_ >> std::setbase(16) >> hex;
        buffer = buffer.insert(i, 1, (char)hex);
        
        if (!hex) {
            last_zero = i;
            if (!first_last_still_zero) {
                first_zero = last_zero;
                first_last_still_zero = true;
            }
        }
        else {
            first_last_still_zero = false;
        }
    }
    if (!first_last_still_zero) {
        first_zero = BLOCK_SIZE;
    }
    buffer = buffer.substr(0, first_zero);
}

string Disk::ReadFromBlock(int block_id) {
    string buffer;
    ReadFromBlock(block_id, buffer);
    return buffer;
}

string Disk::Read(int start_block_id) {
    int block_itr = start_block_id;
    string content = "";
    while(block_itr != GetFileEndFlag()) {
        content += ReadFromBlock(block_itr);
        block_itr = GetFatValue(block_itr);
    }
    return content;
}

int Disk::GetFatBytes() {
    int capacity_length = GetCapacityLength();
    return (capacity_length + 1) * capacity_ + 2;
}

int Disk::GetCapacityLength() {
    string capacity_str = std::to_string(capacity_);
    return capacity_str.length();
}

void Disk::UpdateFat(int block_id, int value) {
    if (block_id < 0) return;
    fat_cache_[block_id] = value;
}

void Disk::SyncFat() {
    file_stream_.seekg(0, ios::beg);
    for(auto i : fat_cache_) {
        file_stream_ << std::setfill('0') << std::setw(2);
        file_stream_ << std::setbase(16) << i.second;
        file_stream_ << " ";
    }
}

void Disk::Sync() {
    SyncFat();
    file_stream_.flush();
}

int Disk::GetBlockOffset(int block_id) {
    return (block_id) * BLOCK_SIZE * 3;
}

int Disk::GetFatValue(int block_id) {
    return fat_cache_[block_id];
}

int Disk::GetFileEndFlag() {
    return 255;
}

void Disk::AppendToBlock(int block_id, string content) {
    int offset = GetBlockOffset(block_id);
    file_stream_.seekp(offset, ios::beg);
    int buf = 1;
    while (buf) {
        file_stream_ >> std::setbase(16) >> buf;
    }
    file_stream_.seekp(-2, ios::cur);

    for (unsigned int i = 0; i < content.length(); i ++) {
        unsigned char cur_char = content[i];
        file_stream_ << std::setbase(16) << (int)cur_char << " ";
    }
}

std::vector<int> Disk::ReadCodeFromBlock(int block_id) {
    std::vector<int> codes;
    int buf = 0;
    file_stream_.seekg(GetBlockOffset(block_id), ios::beg);
    for (unsigned int i = 0; i < BLOCK_SIZE; i ++) {
        file_stream_ >> std::setbase(16) >> buf;
        codes.push_back(buf);
    }
    return codes;
}

bool Disk::WriteCodeToBlock(int block_id, std::vector<int> codes) {
    file_stream_.seekg(GetBlockOffset(block_id), ios::beg);
    if (codes.size() > BLOCK_SIZE) {
        return false;
    }
    for (unsigned int i = 0; i < codes.size() && i < BLOCK_SIZE; i ++) {
        file_stream_ << std::setbase(16) << std::setw(2) << std::setfill('0') << codes[i] << " ";
    }
    return true;
}

bool Disk::DeleteBlock(int block_id) {
    int next_block = 0;
    std::vector<int> empty_block(BLOCK_SIZE);
    std::fill(empty_block.begin(), empty_block.end(), 0);
    while (block_id != 0) {
        WriteCodeToBlock(block_id, empty_block);
        next_block = GetFatValue(block_id);
        UpdateFat(block_id, 0);
        block_id = next_block;
    }
    return true;
}

void Disk::ClearBlock(int block_id) {
    file_stream_.seekp(GetBlockOffset(block_id) ,ios::beg);
    for (int i = 0; i < BLOCK_SIZE; i ++) {
        file_stream_ << "00 ";
    }
    UpdateFat(block_id, 0);
}

bool Disk::Update(int start_block_id, string content) {
    int data_length = content.length();
    int content_ptr = 0;
    int front_block = -1;
    int subcontent_length = 0;
    int next_block;

    string subcontent;
    int cur_block_id = start_block_id;
    front_block = cur_block_id;
    
    while(cur_block_id != GetFileEndFlag()) {
        
        // 在原占用盘块上操作
        subcontent_length = data_length>BLOCK_SIZE?BLOCK_SIZE:data_length;
        subcontent = content.substr(content_ptr, subcontent_length);
        content_ptr = content_ptr + subcontent_length;
        
        next_block = GetFatValue(cur_block_id);
        if (data_length > 0){
            WriteToBlock(cur_block_id, subcontent);
            front_block = cur_block_id;
            data_length -= subcontent_length;
        }
        else {
            ClearBlock(cur_block_id);
        }
        
        // 确定该文件是否有下一个盘块         
        if (next_block == GetFileEndFlag()) {
            break;
        }       
        
        cur_block_id = next_block;
        
    }
    
    // 在新盘块上操作
    if (data_length > 0) {
        for(unsigned int itr = 0;itr < (unsigned)fat_size_ && data_length > 0; itr ++) {
            if (GetFatValue(itr) == 0) {
                subcontent_length = data_length>BLOCK_SIZE?BLOCK_SIZE:data_length;
                subcontent = content.substr(content_ptr, subcontent_length);
                content_ptr = content_ptr + subcontent_length;
                data_length -= subcontent_length;
                
                WriteToBlock(itr, subcontent);
                UpdateFat(front_block, itr);
                front_block = itr;
            }
        }
    }
    UpdateFat(front_block, GetFileEndFlag());
    return true;
}

void Disk::Clear(int start_block_id) {
    int cur_block_id = start_block_id;
    int next_block_id;
    while(cur_block_id != GetFileEndFlag()) {
        next_block_id = GetFatValue(cur_block_id);
        ClearBlock(cur_block_id);
        cur_block_id = next_block_id;
    }
}

double Disk::GetDiskUsedRatio() {
    double used = 0;
    for (int i = 0; i < GetFatSize(); i ++) {
        if (GetFatValue(i) != 0) used ++;
    }
    return used / GetFatSize();
}
